package velox.jcr.mapper.spring;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;
import org.springmodules.jcr.JcrCallback;
import org.springmodules.jcr.JcrTemplate;
import org.springmodules.jcr.SessionFactory;
import org.springmodules.jcr.SessionFactoryUtils;

import velox.jcr.mapper.AccessDeniedException;
import velox.jcr.mapper.MapperManager;
import velox.jcr.mapper.NodeRef;
import velox.jcr.util.NodeUtils;
import velox.jcr.util.PathUtils;

public class MapperTemplate extends JcrTemplate implements MapperOperations {
	private MapperManager mapperManager;

	@Autowired protected PlatformTransactionManager transactionManager;
	
	public MapperTemplate() {
		super();
	}
	
	public MapperTemplate(SessionFactory sessionFactory, MapperManager mapperManager) {
		super();

		setSessionFactory(sessionFactory);
		setMapperManager(mapperManager);
		afterPropertiesSet();
	}


	public void setMapperManager(MapperManager mapperManager) {
		this.mapperManager = mapperManager;
	}

	@Override
	public void save() {
		try {
			this.mapperManager.save();
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}		
	}
	
	public Object execute(MapperCallback callback) {
		try {
			return callback.doWithMapperManager(mapperManager);
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}
	}

	public void remove(Object o) {
		try {
			this.mapperManager.remove(o);
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}
	}
	
	public <T> T getObjectByUUID(String id, Class<T> type) {
		try {
			return this.mapperManager.get(new NodeRef(id), type);
		} catch (RepositoryException e) {
			e.printStackTrace();
			throw SessionFactoryUtils.translateException(e);
		}
	}
	
	public void insert(String path, Object o) {
		String parentPath = PathUtils.getPrefix(path);
		if (parentPath.equals("")) parentPath = "/";
		
		String name = PathUtils.getSuffix(path);
		try {
			Node n = (Node) getItem(parentPath);
			this.mapperManager.persist(new NodeRef(n), name, o);
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}
	}
	
	public void update(Object o) {
		try {
			this.mapperManager.update(o);
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}
	}

	public <T> T getObject(String path, Class<T> type) {
		try {
			Node n = (Node) getItem(path);
			return this.mapperManager.get(new NodeRef(n), type);
		} catch (DataRetrievalFailureException drfe) {
			if (drfe.getCause() instanceof PathNotFoundException) {
				throw new ObjectNotFoundException(drfe);
			} else {
				throw drfe;
			}
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}
	}

	public <T> List<T> getChildObjects(String path, Class<T> type) {
		try {
			Node n = (Node) getItem(path);
			NodeIterator ni = n.getNodes();
			List<T> dest = new ArrayList<T>();
			while (ni.hasNext()) {
				Node child = ni.nextNode();
				try {
					dest.add(this.mapperManager.get(new NodeRef(child), type));
				} catch (AccessDeniedException ade) {
					// Ignore
				}
			}
			
			return dest;

		} catch (DataRetrievalFailureException drfe) {
			if (drfe.getCause() instanceof PathNotFoundException) {
				throw new ObjectNotFoundException(drfe);
			} else {
				throw drfe;
			}
		} catch (RepositoryException e) {
			throw SessionFactoryUtils.translateException(e);
		}
	}
	
	public void dump() {
		TransactionTemplate tmpl = new TransactionTemplate(transactionManager);
		tmpl.execute(new TransactionCallback() {
			public Object doInTransaction(TransactionStatus arg0) {
				execute(new JcrCallback() {
					public Object doInJcr(Session session) throws IOException,
							RepositoryException {
						NodeUtils.dump(session.getRootNode());
						return null;
					}
				});
				return null;
			}
		});
	}
	
	public static class ObjectNotFoundException extends RuntimeException {
		public ObjectNotFoundException(Throwable arg0) {
			super(arg0);
		}
	}
}
